Swift下的状态设计模式

如果你的对象拥有许多状态,那么你或许可以考虑一下使用状态模式。在这篇博文中,我们将覆盖到一些关于状态模式的一些理论,然后会以一个列子说明如何实现它。希望,阅读完这篇文章以后,你会比较熟悉状态设计模式。

状态设计模式

当你在做项目的时候,很有可能碰到一个类有很多内部状态。举个栗子,比方说你有一个从服务器下载大图的类。这个类就有可能处于好几种不同的状态:请求中、下载中、处理中、保存中、、、等等。

在我们的例子中我们将使用一辆汽车。我们的汽车可以处于停止的状态,它也能够处于移动中、或者自动停车状态。这辆汽车将会有它的功能,比方说刹车和停车。根据汽车所处的状态,使用不同的功能将会有不一样的效果。如果对停止状态的汽车发送刹车指令,将没有任何效果;而对一个正在自动停车状态的车发送刹车指令,将会使汽车取消它的停车操作。

这辆汽车能够内部控制它自己的状态,我们不需要知道,或者无须关心它到底处于什么状态。从本文前面的说明,你知道,调用同一个功能,但对象处于不同的状态的时候,会产生不一样的结果。这也给我们带来了状态设计模式的定义如下:

状态设计模式允许一个对象在它的内部状态改变的时候,改变它自己的行为。这个对象看起来好像能够改变它的类型

到这里为止,已经足够覆盖状态设计模式的理论。接下来,让我们先看一下几幅图表。

汽车状态

我们将要在一个汽车类上实现“状态设计模式”。我们的汽车将会有三个不一样的状态,如图1-1

汽车能够改变状态,但是有几个规则需要遵守。例如,你不能直接从移动状态变为停车状态,你需要先转换到停止状态。

如果你正在思考如何将汽车的这些行为实现到你的汽车类当中,你也许会想到用一个枚举来存储汽车类的状态信息。想象一下你的函数将会是怎么样的,你的代码中会出现很多switch cases。你的汽车类将会变的相当庞大,并且会难以维护。

我们将要做的是,将每个’cases’或者状态独立成它自己的一个类。汽车类将会有一个对当前状态的引用。这将允许它们控制汽车的当前状态。下面图1-2,是一个关于我们类的层次的简要图。

让我们具体来研究一下它,并且开始着手写代码。。。

代码

我们将会尽量保持代码简单,便于理解。我们的汽车类将会有三个函数,一个用来存储速度的变量和一些创建状态的工厂方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Foundation

protocol VehicleProtocol: class
{
// MARK: - Vehicle State
var speed: Int { get set }
func setState(_ state: VehicleState)

// MARK: - Vehicle Controls
func accelerate()
func brake()
func park()

// MARK: - State Getters
func getStoppedState() -> VehicleState
func getMovingState() -> VehicleState
func getParkingState() -> VehicleState
}

一个使用汽车的类,一般情况下只能通过调用‘vehicle controls’中的函数的方式,与汽车交互。 其他的函数/变量只能被状态类使用。

调用任何控制函数只是简单的将汽车转变为目标状态,而当前的汽车状态会具体处理需要做的事情;

1
2
3
4
5
6
7
8
9
10
11
func accelerate() {
state?.accelerate()
}

func brake() {
state?.brake()
}

func park() {
state?.park()
}

我们的汽车使用工厂方法来创建汽车状态;

1
2
3
4
5
6
7
8
9
10
11
12
// MARK: - State Getters
func getStoppedState() -> VehicleState {
return StoppedState(self)
}

func getMovingState() -> VehicleState {
return MovingState(self)
}

func getParkingState() -> VehicleState {
return ParkingState(self)
}

现在你将在这里开始看到模式的合并,也就是将所有状态串起来(不知道这么说合不合适)。每个状态类有一个指回汽车的指针,而且它能够创建新的状态。到现在为止,就差一口气了,那就是实现这些状态类。

状态类(State Classes)

我们将针对我们的汽车状态使用一个协议(Protocol):

1
2
3
4
5
6
7
8
9
10
  import Foundation

protocol VehicleState
{
init(_ vehicle: VehicleProtocol)

func accelerate()
func brake()
func park()
}

每个状态函数都会有一个指回汽车类的引用。并且每个状态类将会实现汽车的控制函数。我们将会在接下来的瞬间看到,每个不一样的状态类,调用汽车的(可能是相同的)控制函数,将会有不同的意义和作用。

停止状态(Stopped)

让我们先看一下最简单的状态,停止状态;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  class StoppedState: VehicleState
{
private weak var vehicle: VehicleProtocol?

required init(_ vehicle: VehicleProtocol) {
self.vehicle = vehicle
}

func accelerate() {
self.vehicle?.speed += 5
if let movingState = self.vehicle?.getMovingState() {
self.vehicle?.setState(movingState)
}
}

func brake() {
print("Can't brake... Vehicle is already stopped!")
}

func park() {
if let parkingState = self.vehicle?.getParkingState() {
self.vehicle?.setState(parkingState)
parkingState.park()
}
}
}

当汽车在停止状态的时候,调用‘accelerate’方法,将会提高汽车的速度5(kph,具体多少并不重要)。它同时也会使得汽车进入新的状态,也就是移动状态。调用‘brake’函数不会有任何作用,因为汽车已经本来就是停止状态。而调用‘park’函数将会使得汽车进入自动停车的过程。

停车状态(Parking)

停车状态稍微有点意思;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ParkingState: VehicleState
{
private weak var vehicle: VehicleProtocol?
private var parking: Bool = false

required init(_ vehicle: VehicleProtocol) {
self.vehicle = vehicle
}

func accelerate() {
print("Vehicle is automatically parking, you can't accelerate!")
}

func brake() {
print("Automatic parking has been aborted")
stopParking()
}

func park() {
guard self.parking == false else {
print("Vehicle is already parking")
return
}
print("Vehicle is now parking")
self.parking = true
DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
self.stopParking()
}
}

private func stopParking() {
print("Vehicle has stopped parking")
self.parking = false
if let stoppedState = self.vehicle?.getStoppedState() {
self.vehicle?.setState(stoppedState)
}
}
}

在这里,我们可以看到‘brake’函数将会停止汽车的停车过程。有趣的是,调用‘park’函数将会引发一系列的事件。你可以对这个方法的内容提出异议,也就是针对‘停止状态’可以有更好的方法,但是为了简单起见,让我们暂时保持这样(Take it easy!)。

移动状态(Moving)

移动状态也同样非常简单;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MovingState: VehicleState
{
private weak var vehicle: VehicleProtocol?

required init(_ vehicle: VehicleProtocol) {
self.vehicle = vehicle
}

func accelerate() {
self.vehicle?.speed += 5
}

func brake() {
self.vehicle?.speed -= 5
if self.vehicle?.speed == 0, let stoppedState = self.vehicle?.getStoppedState() {
print("Vehicle braked to a stop")
self.vehicle?.setState(stoppedState)
}
}

func park() {
print("Can't park the vehicle while it's moving. You need to stop first")
}
}

我们可以在这里看到,我们在‘brake’函数中有一小段不一样的逻辑,就是,如果速度掉到0,将会使得汽车进入到停止状态。

测试

让我们迫不及待的测试一下,我们的汽车:

1
2
3
4
5
6
7
8
9
10
private func testVehicle() {
let vehicle = Vehicle()
vehicle.brake()
vehicle.accelerate()
vehicle.accelerate()
vehicle.brake()
vehicle.park() // prints: Can't park the vehicle while it's moving. You need to stop first
vehicle.brake()
vehicle.park()
}

我们可以看到,我们第一次调用‘park’方法将会打印出一条消息,意思是“汽车必须是停止状态,才能够停车”,当我们刹车以后,第二次调用,才会产生预期的结果。这个简单的测试表现出了我们的汽车是如何在运行中改变状态的。调用同一个类实例的同一个方法会产生不一样的结果。哈哈,这就是状态模式的精髓。

总结

当你看到一个类有很多的switch-case语句的时候,就表示你可以考虑一下使用状态模式。我们可以很简单的使用枚举来表示汽车的当前状态。但是函数会变得很庞大,业务逻辑将会出现在case代码快里边,代码很有可能会变得杂乱而难以维护。这个简单的设计模式也许能够轻松的解决这个问题。当然,随着项目的发展,你会有很多很多的类,类的层次会很复杂。但是别担心,用了状态模式,你的代码照样能够易于理解,可扩展。不信,你可以试一试!毕竟没有什么损失。

我希望今天你能在这学到一些新东西,新知识。
和往常一样,希望有一个开心的日子😊